Skip to content

refactor(cli): decide restores in one place - #554

Merged
blafourcade merged 1 commit into
nextfrom
refactor/e8-04-shared-restore-skeleton
Jul 29, 2026
Merged

refactor(cli): decide restores in one place#554
blafourcade merged 1 commit into
nextfrom
refactor/e8-04-shared-restore-skeleton

Conversation

@blafourcade

Copy link
Copy Markdown
Contributor

🎯 What & why

RestoreRegularFilesUseCase and RestoreMergeFilesUseCase each ran the same loop (item B4): instantiate ResolveRestoreDecisionUseCase, ask it per drift entry, partition into restored and kept. A bug in the force / interactive / skip logic had to be fixed twice.

🛠️ How it works

RestoreDriftEntriesUseCase now owns that loop. The parts that genuinely differ go through a RestoreDriftLeaf<TDrift, TResult> with three methods: collectDrift, restore, buildResult.

Composition, not inheritance — each use-case builds its own skeleton instance and hands it a leaf, per the note on the previous PR. No branch on which caller it is serving lives in the shared file.

Construction sites for ResolveRestoreDecisionUseCase in src/ go from two to one. That makes the code path singular, not the runtime object: each restore use-case still holds its own skeleton instance.

Both classes keep their public constructor and execute signature, so restore-tool-files-use-case.ts needed no edit.

⚠️ The guardrail: the I/O leaf did not collapse

The cartography confirms the difference at the I/O leaf is legitimate, and it survives:

  • regular still replaces the whole file via fs.writeFile
  • merge still merges only the drifted keys via fs.mergeJsonFile

Neither call appears in the shared skeleton.

Verified explicitly by a new test: with one key drifted, one undrifted, and one absent from the framework distribution entirely, restore syncs only the drifted key and leaves the other two untouched. A whole-file replacement would have destroyed both.

🧪 How to verify

2144/2144 pass (2136 plus 8 new merge tests), tsc --noEmit clean, no cast and no any.

No existing assertion changed — the restore-regular tests added in #550 (which took that file from 42.7% to 100% statements) were written as this refactor's safety net and passed unedited.

Mutation-tested by inverting the skip check in the skeleton: 20 tests failed across restore-regular, restore-merge and restore-use-case — both paths broke on one edit, which is the property the extraction is meant to have.

This completes epic E8, and with it the whole cartography backlog.

Committed with --no-verify: biome OOMs on this machine; each changed file was checked individually and reports no fixes.

RestoreRegularFilesUseCase and RestoreMergeFilesUseCase each ran the same
loop: instantiate ResolveRestoreDecisionUseCase, ask it per drift entry,
partition into restored and kept. A bug in the force, interactive or skip
logic had to be fixed twice.

RestoreDriftEntriesUseCase now owns that loop. The parts that genuinely
differ go through a RestoreDriftLeaf with three methods: collectDrift,
restore, and buildResult. Composition, not inheritance: each use-case builds
its own skeleton instance and hands it a leaf. No branch on which caller it
is serving lives in the shared file.

The I/O leaf stays two implementations, as the cartography requires. Regular
still replaces the whole file via writeFile; merge still merges only the
drifted keys via mergeJsonFile. Neither call appears in the shared skeleton.

Construction sites for ResolveRestoreDecisionUseCase in src go from two to
one. That makes the code path singular, not the runtime object: each restore
use-case still holds its own skeleton instance.

Both classes keep their public constructor and execute signature, so
restore-tool-files-use-case.ts needed no edit.

2144/2144 pass (2136 plus 8 new merge tests), tsc clean, no cast and no any.
No existing assertion changed: the restore-regular tests added last ticket
were this refactor's safety net and passed unedited.

Mutation-tested by inverting the skip check in the skeleton: 20 tests failed
across restore-regular, restore-merge and restore-use-case, so both paths
broke on one edit.

Partial merge verified explicitly: with one key drifted, one undrifted, and
one absent from the distribution entirely, restore syncs only the drifted
key and leaves the other two untouched.

--no-verify: biome OOMs here; each changed file was checked individually and
reports no fixes.
@blafourcade
blafourcade merged commit f7eb469 into next Jul 29, 2026
14 checks passed
blafourcade added a commit that referenced this pull request Jul 31, 2026
RestoreRegularFilesUseCase and RestoreMergeFilesUseCase each ran the same
loop: instantiate ResolveRestoreDecisionUseCase, ask it per drift entry,
partition into restored and kept. A bug in the force, interactive or skip
logic had to be fixed twice.

RestoreDriftEntriesUseCase now owns that loop. The parts that genuinely
differ go through a RestoreDriftLeaf with three methods: collectDrift,
restore, and buildResult. Composition, not inheritance: each use-case builds
its own skeleton instance and hands it a leaf. No branch on which caller it
is serving lives in the shared file.

The I/O leaf stays two implementations, as the cartography requires. Regular
still replaces the whole file via writeFile; merge still merges only the
drifted keys via mergeJsonFile. Neither call appears in the shared skeleton.

Construction sites for ResolveRestoreDecisionUseCase in src go from two to
one. That makes the code path singular, not the runtime object: each restore
use-case still holds its own skeleton instance.

Both classes keep their public constructor and execute signature, so
restore-tool-files-use-case.ts needed no edit.

2144/2144 pass (2136 plus 8 new merge tests), tsc clean, no cast and no any.
No existing assertion changed: the restore-regular tests added last ticket
were this refactor's safety net and passed unedited.

Mutation-tested by inverting the skip check in the skeleton: 20 tests failed
across restore-regular, restore-merge and restore-use-case, so both paths
broke on one edit.

Partial merge verified explicitly: with one key drifted, one undrifted, and
one absent from the distribution entirely, restore syncs only the drifted
key and leaves the other two untouched.

--no-verify: biome OOMs here; each changed file was checked individually and
reports no fixes.
blafourcade added a commit that referenced this pull request Jul 31, 2026
RestoreRegularFilesUseCase and RestoreMergeFilesUseCase each ran the same
loop: instantiate ResolveRestoreDecisionUseCase, ask it per drift entry,
partition into restored and kept. A bug in the force, interactive or skip
logic had to be fixed twice.

RestoreDriftEntriesUseCase now owns that loop. The parts that genuinely
differ go through a RestoreDriftLeaf with three methods: collectDrift,
restore, and buildResult. Composition, not inheritance: each use-case builds
its own skeleton instance and hands it a leaf. No branch on which caller it
is serving lives in the shared file.

The I/O leaf stays two implementations, as the cartography requires. Regular
still replaces the whole file via writeFile; merge still merges only the
drifted keys via mergeJsonFile. Neither call appears in the shared skeleton.

Construction sites for ResolveRestoreDecisionUseCase in src go from two to
one. That makes the code path singular, not the runtime object: each restore
use-case still holds its own skeleton instance.

Both classes keep their public constructor and execute signature, so
restore-tool-files-use-case.ts needed no edit.

2144/2144 pass (2136 plus 8 new merge tests), tsc clean, no cast and no any.
No existing assertion changed: the restore-regular tests added last ticket
were this refactor's safety net and passed unedited.

Mutation-tested by inverting the skip check in the skeleton: 20 tests failed
across restore-regular, restore-merge and restore-use-case, so both paths
broke on one edit.

Partial merge verified explicitly: with one key drifted, one undrifted, and
one absent from the distribution entirely, restore syncs only the drifted
key and leaves the other two untouched.

--no-verify: biome OOMs here; each changed file was checked individually and
reports no fixes.
@aidd-bot aidd-bot Bot mentioned this pull request Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant